package com.youlu.campus.service.course;

import com.youlu.campus.entity.PlatformConfig;
import com.youlu.campus.entity.course.entity.CourseSimpleActivityEntity;
import com.youlu.campus.service.platform.PlatformConfigService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.Objects;

/**
 * @author : [dengbo]
 * @className : UserActivityService
 * @description : [报名添加每天的统计数据]
 * @createTime : [2022/5/31 18:11]
 */
@Service
@Slf4j
public class UserActivityService {

    private static final String ACTIVITY_REGISTER_NUM = "activity.recommend.num.date:%s";

    private static final String ACTIVITY_REGISTER_SNAPSHOT_NUM = "activity.recommend.num.snapshot";

    private static final int ACTIVITY_REGISTER_EXPIRE = 3600 * 24 * 4;

    private static final String ACTIVITY_COURSE_STUDY_NUM = "activity.course.study.num.courseId:%s.userId:%s";

    private static final String ACTIVITY_RANDOM_NUM = "activity.random.num:%s";
    // 观看数量
    private static final String ACTIVITY_VIEW_RANDOM_NUM = "activity.view.random.num:%s";

    private static final String ACTIVITY_COUPONS_LIST = "activity.coupons.list:%s";

    @Autowired
    private RedisTemplate<String, String> redisTemplate;


    @Autowired
    private PlatformConfigService platformConfigService;


    private String activityUrl = "/h5/index.html?#/pages/activity-detial?activityId=%s&appId=%s&theme=%s&authorizeMode=%s";
    /**
     * 新竞赛活动
     */
    private String activityContestPluUrl = "/c5/index.html?#/?appId=%s&activityId=%s&theme=%s";

    private String activityContestUrl = "/contest-h5/index.html?a=a#/pages/home?appId=%s&activityId=%s";

    @Value("${sys.constentActivity:61834be460d5e86411b654a5}")
    private String constentActivity;


    /**
     * 从活动优惠券集合中弹出一个可用券码（原子随机弹出）
     * 集合不存在或为空时返回 null。
     *
     * @param activityId 活动ID
     * @return 被弹出的券码；无可用券码返回 null
     */
    public String getRedeemCode(String activityId) {
        Objects.requireNonNull(activityId, "activityId must not be null");

        String key = String.format(ACTIVITY_COUPONS_LIST, activityId);
        SetOperations<String, String> setOps = redisTemplate.opsForSet();

        // 使用 RedisCallback 确保在集群模式下对单个 key 执行 SPOP 的原子性
        return redisTemplate.execute((RedisCallback<String>) connection -> {
            byte[] rawKey = key.getBytes(StandardCharsets.UTF_8);
            byte[] result = connection.sPop(rawKey);
            return result == null ? null : new String(result, StandardCharsets.UTF_8);
        });
    }

    /**
     * 获取活动优惠券集合的当前成员数量
     *
     * @param activityId 活动ID
     * @return 成员数量；key 不存在时返回 0
     */
    public long getRedeemCodeNum(String activityId) {
        Objects.requireNonNull(activityId, "activityId must not be null");

        String key = String.format(ACTIVITY_COUPONS_LIST, activityId);
        SetOperations<String, String> setOps = redisTemplate.opsForSet();
        Long size = setOps.size(key);
        return size == null ? 0L : size;
    }

    /**
     * 获取时间段key
     *
     * @param dateTime
     * @return
     */
    public String get18To10(LocalDateTime dateTime) {
        int hour = dateTime.getHour();
        if (hour >= 10 && hour < 18) {
            String start = dateTime.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
            String end = start;
            return start + "10-" + end + "18";
        }
        if (hour >= 18 && hour <= 23) {
            String start = dateTime.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
            String end = dateTime.plusDays(1).format(DateTimeFormatter.ofPattern("yyyyMMdd"));
            return start + "18-" + end + "10";
        }
        String start = dateTime.minusDays(1).format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String end = dateTime.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        return start + "18-" + end + "10";
    }

    public String get10Key() {
        LocalDate localDate = LocalDate.now();
        LocalDateTime localDateTime = localDate.atTime(9, 0);
        return get18To10(localDateTime);
    }

    public String get18Key() {
        LocalDate localDate = LocalDate.now();
        LocalDateTime localDateTime = localDate.atTime(12, 0);
        return get18To10(localDateTime);
    }

    /**
     * 判断学习完成
     *
     * @return
     */
    /**
     * 判断学习是否完成：对课程学习计数自增1，若达到 total 则删除计数键并返回 true。
     *
     * @param courseId 课程ID
     * @param userId   用户ID
     * @param total    完成阈值
     * @return 达到阈值返回 true；否则返回 false
     */
    public boolean hasStudyComplete(String courseId, String userId, int total) {
        Objects.requireNonNull(courseId, "courseId must not be null");
        Objects.requireNonNull(userId, "userId must not be null");

        String key = String.format(ACTIVITY_COURSE_STUDY_NUM, courseId, userId);

        // Lua 脚本保证 incrby 与 del 的原子性（集群模式安全）
        String script =
                "local n = redis.call('incrby', KEYS[1], 1) " +
                        "if n >= tonumber(ARGV[1]) then " +
                        "  redis.call('del', KEYS[1]) " +
                        "  return 1 " +
                        "else " +
                        "  return 0 " +
                        "end";

        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long result = redisTemplate.execute(redisScript, Collections.singletonList(key), String.valueOf(total));

        boolean completed = result != null && result == 1L;
        log.info("UserActivityService hasStudyComplete, course study, key={}, completed={}, result={}", key, completed, result);
        return completed;
    }

    /**
     * 删除学习完成计数键（一般用于补偿或手动清理）
     *
     * @param courseId 课程ID
     * @param userId   用户ID
     */
    public void delStudyCompleteNum(String courseId, String userId) {
        Objects.requireNonNull(courseId, "courseId must not be null");
        Objects.requireNonNull(userId, "userId must not be null");

        String key = String.format(ACTIVITY_COURSE_STUDY_NUM, courseId, userId);
        redisTemplate.delete(key);
    }

    /**
     * 获取活动随机数（数值型）
     *
     * @param activityId 活动ID
     * @return 数值；不存在返回 0
     */
    public long getActivityRandomNum(String activityId) {
        Objects.requireNonNull(activityId, "activityId must not be null");

        String key = String.format(ACTIVITY_RANDOM_NUM, activityId);
        String numStr = redisTemplate.opsForValue().get(key);
        return StringUtils.isNotBlank(numStr) ? Long.valueOf(numStr) : 0L;
    }

    /**
     * 获取活动浏览随机数（数值型）
     *
     * @param activityId 活动ID
     * @return 数值；不存在返回 0
     */
    public long getActivityViewRandomNum(String activityId) {
        Objects.requireNonNull(activityId, "activityId must not be null");

        String key = String.format(ACTIVITY_VIEW_RANDOM_NUM, activityId);
        String numStr = redisTemplate.opsForValue().get(key);
        return StringUtils.isNotBlank(numStr) ? Long.valueOf(numStr) : 0L;
    }

    /**
     * 删除活动随机数
     *
     * @param activityId 活动ID
     */
    public void deleteActivityRandomNum(String activityId) {
        Objects.requireNonNull(activityId, "activityId must not be null");

        String key = String.format(ACTIVITY_RANDOM_NUM, activityId);
        redisTemplate.delete(key);
    }

    /**
     * 设置活动随机数
     *
     * @param activityId 活动ID
     * @param num        数值
     */
    public void setActivityRandomNum(String activityId, Long num) {
        Objects.requireNonNull(activityId, "activityId must not be null");
        Objects.requireNonNull(num, "num must not be null");

        String key = String.format(ACTIVITY_RANDOM_NUM, activityId);
        redisTemplate.opsForValue().set(key, num.toString());
    }

    /**
     * num为负数表示减
     *
     * @param activityId
     * @param num
     */
    /**
     * 按天累加注册人数：支持正数累加、负数扣减
     *
     * @param activityId 活动ID
     * @param num        增量（可为负数）
     */
    public void addRegisterActivityNum(String activityId, int num) {
        Objects.requireNonNull(activityId, "activityId must not be null");

        LocalDateTime now = LocalDateTime.now();
        String dateKey = get18To10(now); // 格式：yyyyMMddHHmmssSSS ->yyyyMMddHHmmss
        String key = String.format(ACTIVITY_REGISTER_NUM, dateKey);

        log.info("addRegisterActivityNum start activityId:{}, key:{}, num:{}", activityId, key, num);

        // 使用事务确保“检查是否存在 + 原子自增 + 条件过期”在同一连接内执行
        redisTemplate.execute((RedisCallback<Void>) connection -> {
            byte[] rawKey = key.getBytes(StandardCharsets.UTF_8);

            // 1) 检查 key 是否已存在（避免首次之外的过期设置）
            Boolean exists = connection.exists(rawKey);
            if (!ObjectUtils.nullSafeEquals(exists, Boolean.TRUE)) {
                // 2) 执行 zincrby（支持负数）
                connection.zIncrBy(rawKey, num, activityId.getBytes(StandardCharsets.UTF_8));

                // 3) 首次创建后设置过期时间
                if (ACTIVITY_REGISTER_EXPIRE > 0) {
                    connection.expire(rawKey, ACTIVITY_REGISTER_EXPIRE);
                }
            } else {
                // key 已存在，直接累加（已有过期时间，无需重复设置）
                connection.zIncrBy(rawKey, num, activityId.getBytes(StandardCharsets.UTF_8));
            }
            return null;
        });

        log.info("addRegisterActivityNum activityId:{}, key:{}, num:{} executed", activityId, key, num);
    }

    public String getH5Url(PlatformConfig platformConfig, CourseSimpleActivityEntity activityInfo) {
        String wechatUrl = "contest-plus".equals(activityInfo.getType()) ?
                String.format(this.activityContestPluUrl, activityInfo.getWechatAppId(), activityInfo.getId(), activityInfo.getThemeColor()) :
                String.format(this.activityUrl, activityInfo.getId(), activityInfo.getWechatAppId(), activityInfo.getThemeColor(), activityInfo.getAuthorizeMode());
        String domain = "";
        String catalogue = "";
        String type = "";
        if (constentActivity.contains(activityInfo.getId())) {
            wechatUrl = String.format(this.activityContestUrl, activityInfo.getWechatAppId(), activityInfo.getId());
            domain = platformConfig.getContestBizFrontDomain();
            catalogue = platformConfig.getContestCatalogue();
            type = "C";
            if (wechatUrl.contains("/h5/")) {
                wechatUrl = wechatUrl.replace("/h5/", "/contest-h5/");
            }
        } else {
            domain = platformConfig.getBizFrontDomain();
            if ("contest-plus".equals(activityInfo.getType())) {
                catalogue = platformConfig.getContestPlusCatalogue();
                type = "C-PLUS";
            } else {
                catalogue = platformConfigService.getCatalogue();
                type = "A";
            }
        }
        return domain + platformConfigService.replaceActiveCatalogue(wechatUrl, catalogue, type);
    }
}
